综述
volley有三个层次的线程,分别为main thread、cache thread 以及network thrads。RequestQueue会维护一个缓存调度线程和一个网络调度线程,当一个Request被加入到队列中时,cache线程会对其进行筛选,如果这个请求的内容可以在缓存中找到,cache线程会亲自解析相应内容,并分发到UI线程。如果缓存中没有,这个请求就会被加入到NetWorkQueue中,所有真正准备进行网络通信的Request都在这里,第一个可用的net线程会从Queue中取出一个请求发送给服务器。当响应数据到的时候,这个net线程会解析原始响应数据并缓存,随后将解析的结果发送给主线程。
基本使用
首先在使用中,我们会在应用中创建一个请求队列
1 | mRequestQueue = Volley.newRequestQueue(this); |
newRequestQueue的实现如下:
1 | //volley.java |
在创建请求队列中,我们还会创建一个BasicNetWork 这是volley使用的网络请求库,通过一个httpstack 对象来初始化,HttpStack是一个网络请求的接口。可以看到,在SDK版本小于9时默认使用的是HttpClient ,否则就用HttpUrlConnection当做其请求库。随后创建完queue后就启动该请求队列。在看start之前我们先看看RequestQueue类。
1 | public class RequestQueue { |
RequestQueue包含了多个队列和调度器以及一个ResponseDelivery转发器,这里NetworkDispatcher和CacheDispatcher都是继承自Thread类。
构造方法
1 | public RequestQueue(Cache cache, Network network, int threadPoolSize, |
再来看start方法:
1 | //RequestQueue.java |
在start中,创建了缓存调度线程(一个)和网络调度线程(四个),并启动它们。也就是说,在我们创建了请求队列后,就有5个线程在后台运行,不断等待网络请求的到来。我们创建好请求队列后,我们一般会add到该队列,所以看下add的源码
1 | public Request add(Request request) { |
这里主要针对需要缓存的request说下,volley默认的request是需要缓存的,在需要缓存时,主要针对mWaitingRequests这个map表进行操作。这map表主要用来记录对应每个请求目前的请求队列,因为volley在网络出现问题后会对再次request进行重试,而重试的次数是可以设置的,重试一旦超过设置值就会超时。这里用mWaitingRequests就是来记录重试队列的。在应用层添加完request后,缓存调度线程就可以处理这个request了,所以看看CacheDispatcher的run方法。
1 | //CacheDispatcher |
总体来说CacheDispatcher的处理流程很简单 ,主要的内容在注释我做了说明。在取到一个request后,需要该请求是否存在缓存,该缓存是否过期,是否需要刷新等问题。 接下来在看看NetworkDispatcher这个网络调度线程,它的任务就是从网络请求队列中取出request进行网络请求了。
1 |
|
可以看到网络请求线程的处理更加简单,主要就是从网络请求队列中取出reqeust进行网络请求,并根据设置将响应保存在缓存中,最后转发响应到主线程中。 我们看到在缓存调度线程和网络调度线程中都使用到了ResponseDelivery对象将结果投递到主线程,这里我们看看这个Delivery 。事实上,ResponseDelivery只是一个接口,我们在创建RequestQueue时使用的ExecutorDelivery它实现了该接口。
1 | public RequestQueue(Cache cache, Network network, int threadPoolSize) { |
这里使用通过一个Handler来构造
1 | public ExecutorDelivery(final Handler handler) { |
Dlivery的有两个重载的PostResponse,在缓存调度线程和网络调度线程都使用了两个参数的,三个参数的用在了缓存需要刷新时的情况,这时我们不仅要将结果投递给主线程而且要刷新缓存(其实只是将请求添加到了网络请求队列中),刷新缓存的操作就是通过第三个参数Runable实现的。 我们看看这个ResponseDeliveryRunnable
1 | private class ResponseDeliveryRunnable implements Runnable { |
下面我们看看Request类,这个请求类在我们的主线程中使用,是个抽象类,其子类需要实现
1 | abstract protected Response<T> parseNetworkResponse(NetworkResponse response); |
分别用来解析响应和转发响应
在Request中,有个RetryPolicy成员通过DefaultRetryPolicy构造,这是个重试策略,主要在用在请求需要重新请求时,我们看看
1 |
|
最后我们看看这个RetryPolicy的使用,它主要用在了网络请求类BasicNetWork中,主要看看接口方法的实现
1 |
|
我们看到performRequest是通过一个while循环来实现的,这是为了在发生请求出错是再次进行重试。在request超时、或者认证失败后都会调用attemptRetryOnException,这里就会进行重试。
1 | private static void attemptRetryOnException(String logPrefix, Request<?> request, |
注意这里退出重试循环是通过retry抛出VolleyError异常来结束的
volley的缓存策略
volley的缓存是通过服务端进行控制的,这样的方式比本地存储更加灵活,同服务端进行交互缓存能降低通信量,同时减轻本地缓存的压力。比如之前我们看到了服务端如何通过304的响应码通知内容未修改,这时就不需要再进行请求了。 volley的缓存类是在创建RequestQueue时指定的,这个缓存类以流的方式实现了Cach Entry的存取。所以我们看看这个Entry
1 | public static class Entry { |
需要说明的是,这个Entry对应我们一个请求的缓存条目,这个是在Request子类根据响应解析出来的。解析类为HttpHeaderParser,当然我们也可以根据需求自己定义类来解析响应得到想要的缓存。下面我们看看这个解析的方法
1 | //HttpHeaderParser.java |
我们在缓存调度线程中判断缓存条目失效的方法为
1 | public boolean isExpired() { |
即当前ttl的值小于当前系统时间就算过期了。所以我们需要服务端设置头部Cache-Control的max-age或者expires来控制缓存的生命时间已达到本地缓存的目的。
总结
volley使用场景 数据量不大但通信频繁的场景